/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */
#include "GameObject.h"

#include "PQException.h"

#include "SceneObject.h"
#include "MeshComponent.h"
#include "TransformComponent.h"

namespace PQEngine
{
	GameObject::MapList GameObject::_list=GameObject::MapList();
	GameObject* GameObject::get(Ogre::SceneNode* node)
	{
		MapList::iterator it=_list.find(node);
		if(it!=_list.end()){
			return it->second;
		}
		return 0;
	}

	GameObject::GameObject(SceneObject* sceneObject,GameObjectType type) : 
	Object(OBJECT_TYPE_GAME_OBJECT),_sceneObject(sceneObject),_gameObjectType(type),
		_name("GameObject"),_isRoot(false),_isMesh(false),_isTerrain(false),_sceneNode(0),_selected(false)
	{
		assert(_sceneObject);

		Ogre::String name="GameObject";
		name+=Ogre::StringConverter::toString(getId());

		for(int i=0;i<COMPONENT_TYPE_COUNT;i++){
			_components[i]=0;
		}

		switch(type){
		case GAME_OBJECT_ROOT:
			_isRoot=true;
			_sceneNode=_sceneObject->getRootSceneNode();
			break;
		case GAME_OBJECT_TERRAIN:
		case GAME_OBJECT_EMPTY:
		case GAME_OBJECT_MESH:
		case GAME_OBJECT_LIGHT:
		case GAME_OBJECT_CAMERA:
		default:
			initEmpty();
			break;
		}
	}

	void GameObject::initEmpty()
	{
		_sceneNode=_sceneObject->createChildSceneNode();
		TransformComponent *component=new TransformComponent();
		component->attach(this);
		
		_sceneObject->getRoot()->addChild(this);//default all other object is a child of root
		_list.insert(GameObject::MapList::value_type(_sceneNode,this));
	}


	int GameObject::getChildrenCount()
	{
		return _children.size();
	}

	GameObject* GameObject::getChild(int index)
	{
		int size=_children.size();
		if(index>=0&&index<size){
			return _children[index];
		}
		return NULL;
	}

	void GameObject::addChild(GameObject *object)
	{
		if(object){
			object->setParent(this);
			
			Ogre::SceneNode* sceneNode=object->getSceneNode();
			Ogre::SceneNode* parentNode=sceneNode->getParentSceneNode();
			parentNode->removeChild(sceneNode);
			_sceneNode->addChild(sceneNode);

			GameObject* parent = object->getParent();
			if(parent){
				parent->removeChild(object);
			}

			_children.push_back(object);
			
		}
	}

	void GameObject::removeChild(GameObject *object)
	{
		GameObjectVector::iterator iter = find(_children.begin(), _children.end(), object);
		if(iter!=_children.end()){
			_children.erase(iter);
		}
	}

	void GameObject::setParent(GameObject* parent)
	{
		_parent=parent;
	}

	GameObject* GameObject::getParent()
	{
		return _parent;
	}

	//recursive update
	void GameObject::update(float deltaT)
	{
		//update game object components
		updateComponents(deltaT);

		//then update child
		updateChildren(deltaT);
	}

	void GameObject::updateComponents(float deltaT)
	{
		//update unique component
		for(int i=0;i<COMPONENT_TYPE_COUNT;i++){
			if(_components[i]){
				_components[i]->update(deltaT);
			}
		}

	}

	void GameObject::updateChildren(float deltaT)
	{
		GameObjectVector::iterator it  =  _children.begin();
		for(;it!=_children.end();++it){
			(*it)->update(deltaT);
		}

	}


	void GameObject::selectObject(bool selected)
	{
		_selected=selected;
		//this->select(selected);
	}

	void GameObject::addComponent(Component *component)
	{
		if(!component){
			return;
		}

		ObjectType type=component->getObjectType();
		switch(type){
			case OBJECT_TYPE_COMPONENT_TRANSFORM:			
				addTransform((TransformComponent*)component);
				return;
			case OBJECT_TYPE_COMPONENT_MESH:			
				addMesh((MeshComponent*)component);
				return;
			default:
				PQThrowShouldNotReach();
				return;
		}
	}

	void GameObject::addMesh(MeshComponent* component)
	{
		Ogre::Entity* entity=component->getEntity();
		_sceneNode->attachObject(entity);
		_components[COMPONENT_TYPE_MESH]=component;
		
		_isMesh=true;
	}


	void GameObject::addTransform(TransformComponent* component)
	{
		assert(component||_components[COMPONENT_TYPE_TRANSFORM]==0);

		setPosition(component->getPosition());
		setOrientation(component->getOrientation());
		setScale(component->getScale());

		_components[COMPONENT_TYPE_TRANSFORM]=component;
		
	}


	//transform component is special because it share by many sub systems
	TransformComponent* GameObject::getTransformComponent()
	{
		Component* comp= _components[COMPONENT_TYPE_TRANSFORM];
		if(comp){
			assert(comp->getComponentType()==COMPONENT_TYPE_TRANSFORM);
			return (TransformComponent*)comp;
		}
		return 0;
	}

	//transform component is special because it share by many sub systems
	MeshComponent* GameObject::getMeshComponent()
	{
		Component* comp= _components[COMPONENT_TYPE_MESH];
		if(comp){
			assert(comp->getComponentType()==COMPONENT_TYPE_MESH);
			return (MeshComponent*)comp;
		}
		return 0;
	}


	void GameObject::setPosition(float x,float y,float z)
	{
		if(_sceneNode){
			_sceneNode->setPosition(x,y,z);
		}
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setPosition(x,y,z);//only save the data not do other thing
		}
	}

	void GameObject::setPosition(Ogre::Vector3 position)
	{
		if(_sceneNode){
			_sceneNode->setPosition(position);
		}
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setPosition(position);
		}
	}

	void GameObject::setOrientation(Ogre::Quaternion orient)
	{
		if(_sceneNode){
			_sceneNode->setOrientation(orient);
		}

		TransformComponent* component=getTransformComponent();
		if(component){
			component->setOrientation(orient);
		}
	}

	void GameObject::setScale(Ogre::Vector3 scale)
	{
		if(_sceneNode){
			_sceneNode->setScale(scale);
		}
		
		TransformComponent* component=getTransformComponent();
		if(component){
			component->setScale(scale);
		}
	}



	bool GameObject::removeComponent(Component *component)
	{
		if(!component){
			return false;
		}
		for(int i=0;i<COMPONENT_TYPE_COUNT;i++){
			if(component==_components[i]){
				Object::removeObject(component->getId());
				return true;
			}
		}
		return false;
	}


	std::string GameObject::getName()
	{
		return _name;
	}

	/*Set current game object's name
	*/
	void GameObject::setName(std::string name)
	{
		_name=name;
	}

	Ogre::Vector3 GameObject::getPosition()
	{
		if(_sceneNode){
			return _sceneNode->getPosition();
		}
		return Ogre::Vector3::ZERO;
	}

	Ogre::Vector3 GameObject::getDerivedPosition()
	{ 
		if(_sceneNode){
			return _sceneNode->_getDerivedPosition(); 
		}
		return Ogre::Vector3::ZERO;
	}

	const Ogre::Quaternion& GameObject::getOrientation()
	{
		if(_sceneNode){
			return _sceneNode->getOrientation(); 
		}
		return Ogre::Quaternion::IDENTITY;
	}

	Ogre::Vector3 GameObject::getScale()
	{
		if(_sceneNode){
			return _sceneNode->getScale(); 
		}
		return Ogre::Vector3::ZERO;
	}

	const Ogre::Vector3 &GameObject::getWorldPosition() const 
	{
		if(_sceneNode){
			return _sceneNode->_getDerivedPosition();
		}
		return Ogre::Vector3::ZERO;
	}

	const Ogre::Quaternion &GameObject::getWorldOrientation() const 
	{
		if(_sceneNode){
			return _sceneNode->_getDerivedOrientation();
		}
		return Ogre::Quaternion::IDENTITY;
	}


	float GameObject::getRadius(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			return mesh->getRadius();
		}else{
			return 0.0f;
		}
	}

	Ogre::Vector3 GameObject::getBoundingBoxSize(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			
			return mesh->getBoundingBoxSize()*getScale();
		}else{
			return Ogre::Vector3(0,0,0);
		}
	}

	Ogre::AxisAlignedBox GameObject::getBoundingBox(void)
	{
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			Ogre::AxisAlignedBox box=mesh->getBoundingBox();
			box.scale(getScale());
			return box;
		}else{
			return Ogre::AxisAlignedBox();
		}
	}

	Ogre::AxisAlignedBox GameObject::getWorldBoundingBox(void)
	{
		
		MeshComponent* mesh=getMeshComponent();
		if(mesh){
			return mesh->getWorldBoundingBox();
		}else{
			return Ogre::AxisAlignedBox::BOX_NULL;
		}
	}

	void GameObject::setDirection( Ogre::Real x, Ogre::Real y, Ogre::Real z, Ogre::Node::TransformSpace relativeTo /*= Ogre::Node::TS_LOCAL*/, const Ogre::Vector3& localDirectionVector /*= Ogre::Vector3::NEGATIVE_UNIT_Z*/ )
	{
		if(_sceneNode){
			_sceneNode->setDirection(x,y,z,relativeTo,localDirectionVector);
		}
	}

	void GameObject::setDirection( const Ogre::Vector3& vec, Ogre::Node::TransformSpace relativeTo , const Ogre::Vector3& localDirectionVector )
	{
		if(_sceneNode){
			_sceneNode->setDirection(vec,relativeTo,localDirectionVector);
		}
	}

	void GameObject::setFixedYawAxis( bool useFixed, const Ogre::Vector3& fixedAxis /*= Ogre::Vector3::UNIT_Y */ )
	{
		if(_sceneNode){
			_sceneNode->setFixedYawAxis(useFixed,fixedAxis);
		}
	}

	void GameObject::yaw( const Ogre::Radian& angle, Ogre::Node::TransformSpace relativeTo /*= Ogre::Node::TS_LOCAL*/ )
	{
		if(_sceneNode){
			_sceneNode->yaw(angle,relativeTo);
		}

	}

	void GameObject::translate( const Ogre::Vector3& d, Ogre::Node::TransformSpace relativeTo /*= Ogre::Node::TS_PARENT*/ )
	{
		if(_sceneNode){
			_sceneNode->translate(d,relativeTo);
		}
	}


}
